import streamlit as st
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import (accuracy_score, mean_squared_error,
                             classification_report, confusion_matrix,
                             r2_score)
from sklearn.datasets import (load_iris, load_wine, load_breast_cancer,
                              load_diabetes, make_classification,
                              make_regression, fetch_california_housing)
import sqlite3
import time
import os

# 设置中文字体
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示问题


# 初始化数据库
def init_db():
    conn = sqlite3.connect('ml_learning_platform.db')
    c = conn.cursor()

    # 创建用户交互记录表
    c.execute('''
    CREATE TABLE IF NOT EXISTS user_interactions (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        algorithm TEXT,
        action TEXT,
        parameters TEXT,
        timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
    )
    ''')

    # 创建示例数据集表
    c.execute('''
    CREATE TABLE IF NOT EXISTS datasets (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        type TEXT,  
        description TEXT
    )
    ''')

    # 插入默认数据集信息（如果不存在）
    datasets = [
        ("鸢尾花数据集", "classification", "包含3种鸢尾花的4个特征，用于分类任务"),
        ("葡萄酒数据集", "classification", "包含3种葡萄酒的13个特征，用于分类任务"),
        ("乳腺癌数据集", "classification", "包含乳腺癌肿瘤的30个特征，用于二分类任务"),
        ("加州住房数据集", "regression", "包含加州住房价格的8个特征，用于回归任务"),
        ("糖尿病数据集", "regression", "包含糖尿病患者的10个特征，用于回归任务")
    ]

    # 检查每个数据集是否已存在，只插入不存在的数据集
    for name, type_, desc in datasets:
        c.execute('SELECT name FROM datasets WHERE name = ?', (name,))
        if not c.fetchone():  # 如果不存在
            c.execute('''
            INSERT INTO datasets (name, type, description) 
            VALUES (?, ?, ?)
            ''', (name, type_, desc))

    conn.commit()
    conn.close()


# 记录用户交互
def log_interaction(algorithm, action, parameters):
    conn = sqlite3.connect('ml_learning_platform.db')
    c = conn.cursor()
    c.execute('''
    INSERT INTO user_interactions (algorithm, action, parameters)
    VALUES (?, ?, ?)
    ''', (algorithm, action, str(parameters)))
    conn.commit()
    conn.close()


# 获取数据集列表
def get_datasets_by_type(data_type):
    conn = sqlite3.connect('ml_learning_platform.db')
    c = conn.cursor()
    c.execute('SELECT name, description FROM datasets WHERE type = ?', (data_type,))
    datasets = c.fetchall()
    conn.close()
    return datasets


# 加载数据集
def load_selected_dataset(dataset_name):
    if dataset_name == "鸢尾花数据集":
        data = load_iris()
    elif dataset_name == "葡萄酒数据集":
        data = load_wine()
    elif dataset_name == "乳腺癌数据集":
        data = load_breast_cancer()
    elif dataset_name == "加州住房数据集":
        data = fetch_california_housing()
    elif dataset_name == "糖尿病数据集":
        data = load_diabetes()
    elif dataset_name == "生成分类数据集":
        X, y = make_classification(n_samples=1000, n_features=10,
                                   n_informative=5, random_state=42)
        return X, y, ["特征" + str(i) for i in range(10)]
    elif dataset_name == "生成回归数据集":
        X, y = make_regression(n_samples=1000, n_features=10,
                               noise=0.1, random_state=42)
        return X, y, ["特征" + str(i) for i in range(10)]

    return data.data, data.target, data.feature_names


# 页面配置
st.set_page_config(
    page_title="机器学习算法学习平台",
    page_icon="📚",
    layout="wide"
)

# 初始化数据库
init_db()

# 侧边栏导航
st.sidebar.title("机器学习算法学习平台")
algorithm = st.sidebar.selectbox(
    "选择算法",
    ["首页", "KNN算法", "线性回归", "逻辑回归", "随机森林", "朴素贝叶斯"]
)

# 首页
if algorithm == "首页":
    st.title("机器学习算法学习平台")
    st.write("""
    欢迎来到机器学习算法学习平台！这个平台旨在帮助您理解和实践各种机器学习算法。

    ### 平台特点
    - 详细的算法原理讲解
    - 可运行的代码示例
    - 交互式算法演示
    - 实时结果可视化

    ### 包含的算法
    - **KNN算法**：一种简单的监督学习算法
    - **线性回归**：用于预测连续值的线性模型
    - **逻辑回归**：用于二分类问题的回归模型
    - **随机森林**：基于决策树的集成学习算法
    - **朴素贝叶斯**：基于贝叶斯定理的概率分类器

    请从左侧菜单选择一个算法开始学习！
    """)

    # 展示平台使用流程
    st.subheader("使用流程")
    col1, col2, col3 = st.columns(3)

    with col1:
        st.info("1. 选择算法")
        st.write("从左侧菜单选择您想学习的算法")

    with col2:
        st.info("2. 学习原理")
        st.write("阅读算法原理和代码示例")

    with col3:
        st.info("3. 互动实践")
        st.write("调整参数，观察算法效果变化")

# KNN算法页面
elif algorithm == "KNN算法":
    st.title("KNN算法学习")

    # 算法原理
    st.header("1. 算法原理")
    st.write("""
    KNN（K-Nearest Neighbors，K近邻）算法是一种简单的监督学习算法，它的核心思想是：**物以类聚，人以群分**。

    ### 基本原理
    KNN算法的工作原理非常直观：
    1. 当需要预测一个新样本时，查看训练集中与该样本最接近的K个样本（近邻）
    2. 对于分类问题，这K个样本中大多数属于哪个类别，新样本就被预测为该类别
    3. 对于回归问题，将这K个样本的目标值取平均值作为新样本的预测值

    ### 距离度量
    计算样本间的"接近程度"通常使用以下距离度量：
    - 欧氏距离：最常用的距离度量，计算两点间的直线距离
    - 曼哈顿距离：计算两点在标准坐标系上的绝对轴距总和
    - 余弦相似度：衡量两个向量方向的相似性

    ### K值的选择
    K值的选择对KNN算法的性能有很大影响：
    - K值过小：模型容易过拟合，对噪声敏感
    - K值过大：模型过于简单，可能欠拟合
    - 通常选择奇数K值以避免投票平局
    """)

    # 代码示例
    st.header("2. 代码示例")
    st.code("""
    # KNN分类示例
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.datasets import load_iris
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score

    # 加载数据
    data = load_iris()
    X, y = data.data, data.target

    # 划分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, random_state=42
    )

    # 创建并训练KNN模型
    knn = KNeighborsClassifier(n_neighbors=5)  # K=5
    knn.fit(X_train, y_train)

    # 预测
    y_pred = knn.predict(X_test)

    # 评估
    accuracy = accuracy_score(y_test, y_pred)
    print(f"准确率: {accuracy:.2f}")
    """, language="python")

    # 交互演示
    st.header("3. 试一试")
    st.write("调整参数，观察KNN算法在不同数据集上的表现")

    # 选择任务类型
    task_type = st.radio("选择任务类型", ["分类", "回归"])

    # 根据任务类型选择数据集
    data_type = "classification" if task_type == "分类" else "regression"
    datasets = get_datasets_by_type(data_type)
    dataset_names = [d[0] for d in datasets]
    dataset_names.append("生成" + task_type + "数据集")

    dataset_name = st.selectbox("选择数据集", dataset_names)

    # 加载数据
    X, y, feature_names = load_selected_dataset(dataset_name)

    # 显示数据集信息
    st.subheader("数据集信息")
    st.write(f"样本数量: {X.shape[0]}, 特征数量: {X.shape[1]}")

    # 显示前5行数据
    st.write("数据预览:")
    df = pd.DataFrame(X, columns=feature_names)
    df["目标值"] = y
    st.dataframe(df.head())

    # 参数设置
    st.subheader("参数设置")
    col1, col2 = st.columns(2)

    with col1:
        test_size = st.slider("测试集比例", 0.1, 0.5, 0.3)

    with col2:
        n_neighbors = st.slider("K值 (近邻数量)", 1, 21, 5, step=2)

    # 距离度量
    metric = st.selectbox("距离度量", ["欧氏距离", "曼哈顿距离", "余弦相似度"])
    metric_map = {
        "欧氏距离": "euclidean",
        "曼哈顿距离": "manhattan",
        "余弦相似度": "cosine"
    }

    # 训练模型
    if st.button("训练模型"):
        # 记录用户交互
        log_interaction(
            "KNN",
            "train_model",
            {
                "task_type": task_type,
                "dataset": dataset_name,
                "test_size": test_size,
                "n_neighbors": n_neighbors,
                "metric": metric
            }
        )

        # 划分训练集和测试集
        X_train, X_test, y_train, y_test = train_test_split(
            X, y, test_size=test_size, random_state=42
        )

        # 创建模型
        with st.spinner("正在训练模型..."):
            time.sleep(1)  # 模拟训练时间

            if task_type == "分类":
                model = KNeighborsClassifier(
                    n_neighbors=n_neighbors,
                    metric=metric_map[metric]
                )
            else:
                model = KNeighborsRegressor(
                    n_neighbors=n_neighbors,
                    metric=metric_map[metric]
                )

            # 训练模型
            model.fit(X_train, y_train)

            # 预测
            y_pred = model.predict(X_test)

        # 显示结果
        st.subheader("模型结果")

        if task_type == "分类":
            # 分类结果
            accuracy = accuracy_score(y_test, y_pred)
            st.success(f"准确率: {accuracy:.4f}")

            # 显示分类报告
            st.text("分类报告:")
            st.text(classification_report(y_test, y_pred))

            # 混淆矩阵
            st.subheader("混淆矩阵")
            cm = confusion_matrix(y_test, y_pred)

            fig, ax = plt.subplots()
            im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
            ax.figure.colorbar(im, ax=ax)

            # 标记坐标轴
            classes = np.unique(y)
            ax.set(xticks=np.arange(cm.shape[1]),
                   yticks=np.arange(cm.shape[0]),
                   xticklabels=classes, yticklabels=classes,
                   title='混淆矩阵',
                   ylabel='真实标签',
                   xlabel='预测标签')

            # 旋转x轴标签
            plt.setp(ax.get_xticklabels(), rotation=45, ha="right",
                     rotation_mode="anchor")

            # 在混淆矩阵中标记数值
            thresh = cm.max() / 2.
            for i in range(cm.shape[0]):
                for j in range(cm.shape[1]):
                    ax.text(j, i, format(cm[i, j], 'd'),
                            horizontalalignment="center",
                            color="white" if cm[i, j] > thresh else "black")

            fig.tight_layout()
            st.pyplot(fig)

        else:
            # 回归结果
            mse = mean_squared_error(y_test, y_pred)
            r2 = r2_score(y_test, y_pred)

            st.success(f"均方误差 (MSE): {mse:.4f}")
            st.success(f"决定系数 (R²): {r2:.4f}")

            # 可视化预测值与真实值
            st.subheader("预测值 vs 真实值")
            fig, ax = plt.subplots(figsize=(10, 6))
            ax.scatter(y_test, y_pred, alpha=0.5)
            ax.plot([y.min(), y.max()], [y.min(), y.max()], 'r--')
            ax.set_xlabel('真实值')
            ax.set_ylabel('预测值')
            ax.set_title('预测值与真实值对比')
            st.pyplot(fig)

            # 残差图
            st.subheader("残差图")
            residuals = y_test - y_pred
            fig, ax = plt.subplots(figsize=(10, 6))
            ax.scatter(y_pred, residuals, alpha=0.5)
            ax.axhline(y=0, color='r', linestyle='--')
            ax.set_xlabel('预测值')
            ax.set_ylabel('残差')
            ax.set_title('残差图')
            st.pyplot(fig)

# 线性回归页面
elif algorithm == "线性回归":
    st.title("线性回归算法学习")

    # 算法原理
    st.header("1. 算法原理")
    st.write("""
    线性回归是一种用于预测连续值目标变量的监督学习算法，它假设特征与目标变量之间存在线性关系。

    ### 基本原理
    线性回归的核心思想是找到一条最佳拟合直线（或超平面），使得预测值与实际值之间的误差最小化。

    对于单特征的情况，线性回归模型可以表示为：
    $$ y = wx + b $$
    其中，$w$ 是权重（斜率），$b$ 是偏置（截距）。

    对于多特征的情况，模型可以表示为：
    $$ y = w_1x_1 + w_2x_2 + ... + w_nx_n + b $$

    ### 损失函数
    线性回归通常使用均方误差（MSE）作为损失函数：
    $$ MSE = \\frac{1}{n} \\sum_{i=1}^{n} (y_i - \\hat{y}_i)^2 $$
    其中，$y_i$ 是实际值，$\\hat{y}_i$ 是预测值。

    ### 求解方法
    求解线性回归模型参数的常用方法有：
    - 最小二乘法：直接通过数学公式求解使损失函数最小的参数
    - 梯度下降法：通过迭代优化找到使损失函数最小的参数
    """)

    # 代码示例
    st.header("2. 代码示例")
    st.code("""
    # 线性回归示例
    from sklearn.linear_model import LinearRegression
    from sklearn.datasets import fetch_california_housing
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import mean_squared_error, r2_score

    # 加载数据（使用加州住房数据集替代波士顿房价数据集）
    data = fetch_california_housing()
    X, y = data.data, data.target

    # 划分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, random_state=42
    )

    # 创建并训练线性回归模型
    model = LinearRegression()
    model.fit(X_train, y_train)

    # 预测
    y_pred = model.predict(X_test)

    # 评估
    mse = mean_squared_error(y_test, y_pred)
    r2 = r2_score(y_test, y_pred)
    print(f"均方误差: {mse:.2f}")
    print(f"决定系数: {r2:.2f}")
    """, language="python")

    # 交互演示
    st.header("3. 试一试")
    st.write("调整参数，观察线性回归算法在不同数据集上的表现")

    # 选择数据集
    datasets = get_datasets_by_type("regression")
    dataset_names = [d[0] for d in datasets]
    dataset_names.append("生成回归数据集")

    dataset_name = st.selectbox("选择数据集", dataset_names)

    # 加载数据
    X, y, feature_names = load_selected_dataset(dataset_name)

    # 显示数据集信息
    st.subheader("数据集信息")
    st.write(f"样本数量: {X.shape[0]}, 特征数量: {X.shape[1]}")

    # 显示前5行数据
    st.write("数据预览:")
    df = pd.DataFrame(X, columns=feature_names)
    df["目标值"] = y
    st.dataframe(df.head())

    # 特征选择（如果特征数量较多）
    if X.shape[1] > 1:
        selected_features = st.multiselect(
            "选择特征（默认使用所有特征）",
            list(feature_names),
            default=list(feature_names)[:2] if len(feature_names) > 2 else list(feature_names)
        )
        # 获取选中特征的索引
        feature_indices = [list(feature_names).index(f) for f in selected_features]
        X = X[:, feature_indices]
        feature_names = selected_features

    # 参数设置
    st.subheader("参数设置")
    test_size = st.slider("测试集比例", 0.1, 0.5, 0.3)

    # 训练模型
    if st.button("训练模型"):
        # 记录用户交互
        log_interaction(
            "线性回归",
            "train_model",
            {
                "dataset": dataset_name,
                "features": selected_features if X.shape[1] > 1 else [feature_names[0]],
                "test_size": test_size
            }
        )

        # 划分训练集和测试集
        X_train, X_test, y_train, y_test = train_test_split(
            X, y, test_size=test_size, random_state=42
        )

        # 创建并训练模型
        with st.spinner("正在训练模型..."):
            time.sleep(1)  # 模拟训练时间
            model = LinearRegression()
            model.fit(X_train, y_train)

            # 预测
            y_pred = model.predict(X_test)

        # 显示结果
        st.subheader("模型结果")

        # 回归指标
        mse = mean_squared_error(y_test, y_pred)
        r2 = r2_score(y_test, y_pred)

        st.success(f"均方误差 (MSE): {mse:.4f}")
        st.success(f"决定系数 (R²): {r2:.4f}")

        # 模型参数
        st.subheader("模型参数")
        params = pd.DataFrame({
            "特征": feature_names + ["偏置项"],
            "系数": list(model.coef_) + [model.intercept_]
        })
        st.dataframe(params)

        # 可视化结果
        st.subheader("可视化结果")

        # 如果是单特征，绘制散点图和回归线
        if X.shape[1] == 1:
            fig, ax = plt.subplots(figsize=(10, 6))
            ax.scatter(X_test, y_test, alpha=0.5, label="实际值")
            ax.plot(X_test, y_pred, 'r-', label="回归线")
            ax.set_xlabel(feature_names[0])
            ax.set_ylabel("目标值")
            ax.set_title(f"{feature_names[0]}与目标值的关系")
            ax.legend()
            st.pyplot(fig)
        else:
            # 多特征时，绘制预测值与真实值对比
            fig, ax = plt.subplots(figsize=(10, 6))
            ax.scatter(y_test, y_pred, alpha=0.5)
            ax.plot([y.min(), y.max()], [y.min(), y.max()], 'r--')
            ax.set_xlabel('真实值')
            ax.set_ylabel('预测值')
            ax.set_title('预测值与真实值对比')
            st.pyplot(fig)

        # 残差图
        st.subheader("残差图")
        residuals = y_test - y_pred
        fig, ax = plt.subplots(figsize=(10, 6))
        ax.scatter(y_pred, residuals, alpha=0.5)
        ax.axhline(y=0, color='r', linestyle='--')
        ax.set_xlabel('预测值')
        ax.set_ylabel('残差')
        ax.set_title('残差图')
        st.pyplot(fig)

# 逻辑回归页面
elif algorithm == "逻辑回归":
    st.title("逻辑回归算法学习")

    # 算法原理
    st.header("1. 算法原理")
    st.write("""
    逻辑回归虽然名称中包含"回归"，但它实际上是一种用于二分类问题的监督学习算法。它通过Sigmoid函数将线性回归的输出映射到[0, 1]区间，从而实现分类。

    ### 基本原理
    逻辑回归的核心是Sigmoid函数（也称为Logistic函数）：
    $$ \\sigma(z) = \\frac{1}{1 + e^{-z}} $$

    对于输入特征$x$，逻辑回归模型可以表示为：
    $$ P(y=1|x) = \\sigma(w^T x + b) = \\frac{1}{1 + e^{-(w^T x + b)}} $$

    其中，$w$ 是权重向量，$b$ 是偏置项。当$P(y=1|x) > 0.5$时，预测为类别1；否则预测为类别0。

    ### 损失函数
    逻辑回归通常使用交叉熵损失函数：
    $$ L(w, b) = -\\frac{1}{n} \\sum_{i=1}^{n} [y_i \\log(\\hat{y}_i) + (1 - y_i) \\log(1 - \\hat{y}_i)] $$

    ### 正则化
    为了防止过拟合，逻辑回归通常会加入正则化项：
    - L1正则化（Lasso）：$\\lambda \\sum |w_i|$
    - L2正则化（Ridge）：$\\lambda \\sum w_i^2$
    - 弹性网络（Elastic Net）：同时使用L1和L2正则化
    """)

    # 代码示例
    st.header("2. 代码示例")
    st.code("""
    # 逻辑回归示例
    from sklearn.linear_model import LogisticRegression
    from sklearn.datasets import load_breast_cancer
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score, classification_report

    # 加载数据
    data = load_breast_cancer()
    X, y = data.data, data.target

    # 划分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, random_state=42
    )

    # 创建并训练逻辑回归模型
    model = LogisticRegression(max_iter=10000)  # 增加迭代次数以确保收敛
    model.fit(X_train, y_train)

    # 预测
    y_pred = model.predict(X_test)

    # 评估
    accuracy = accuracy_score(y_test, y_pred)
    print(f"准确率: {accuracy:.2f}")
    print("分类报告:")
    print(classification_report(y_test, y_pred))
    """, language="python")

    # 交互演示
    st.header("3. 试一试")
    st.write("调整参数，观察逻辑回归算法在不同数据集上的表现")

    # 选择数据集
    datasets = get_datasets_by_type("classification")
    dataset_names = [d[0] for d in datasets]
    dataset_names.append("生成分类数据集")

    dataset_name = st.selectbox("选择数据集", dataset_names)

    # 加载数据
    X, y, feature_names = load_selected_dataset(dataset_name)

    # 显示数据集信息
    st.subheader("数据集信息")
    st.write(f"样本数量: {X.shape[0]}, 特征数量: {X.shape[1]}")
    class_distribution = pd.Series(y).value_counts()
    st.write("类别分布:")
    st.bar_chart(class_distribution)

    # 显示前5行数据
    st.write("数据预览:")
    df = pd.DataFrame(X, columns=feature_names)
    df["目标值"] = y
    st.dataframe(df.head())

    # 参数设置
    st.subheader("参数设置")
    col1, col2 = st.columns(2)

    with col1:
        test_size = st.slider("测试集比例", 0.1, 0.5, 0.3)
        C = st.slider("正则化强度 (C)", 0.01, 10.0, 1.0, help="较小的C表示较强的正则化")

    with col2:
        penalty = st.selectbox("正则化类型", ["l2", "l1", "elasticnet", "none"])
        solver = st.selectbox("求解器", ["lbfgs", "newton-cg", "liblinear", "sag", "saga"])

        # 根据正则化类型和求解器的兼容性调整
        if penalty == "elasticnet" and solver != "saga":
            st.warning("elasticnet正则化仅支持saga求解器，已自动切换为saga")
            solver = "saga"
        if penalty == "l1" and solver not in ["liblinear", "saga"]:
            st.warning("l1正则化仅支持liblinear和saga求解器，已自动切换为liblinear")
            solver = "liblinear"

    # 训练模型
    if st.button("训练模型"):
        # 记录用户交互
        log_interaction(
            "逻辑回归",
            "train_model",
            {
                "dataset": dataset_name,
                "test_size": test_size,
                "C": C,
                "penalty": penalty,
                "solver": solver
            }
        )

        # 划分训练集和测试集
        X_train, X_test, y_train, y_test = train_test_split(
            X, y, test_size=test_size, random_state=42, stratify=y
        )

        # 创建并训练模型
        with st.spinner("正在训练模型..."):
            time.sleep(1)  # 模拟训练时间
            model = LogisticRegression(
                C=C,
                penalty=penalty,
                solver=solver,
                max_iter=10000,
                random_state=42
            )
            model.fit(X_train, y_train)

            # 预测
            y_pred = model.predict(X_test)
            y_pred_proba = model.predict_proba(X_test)

        # 显示结果
        st.subheader("模型结果")

        # 准确率
        accuracy = accuracy_score(y_test, y_pred)
        st.success(f"准确率: {accuracy:.4f}")

        # 分类报告
        st.text("分类报告:")
        st.text(classification_report(y_test, y_pred))

        # 混淆矩阵
        st.subheader("混淆矩阵")
        cm = confusion_matrix(y_test, y_pred)

        fig, ax = plt.subplots()
        im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
        ax.figure.colorbar(im, ax=ax)

        # 标记坐标轴
        classes = np.unique(y)
        ax.set(xticks=np.arange(cm.shape[1]),
               yticks=np.arange(cm.shape[0]),
               xticklabels=classes, yticklabels=classes,
               title='混淆矩阵',
               ylabel='真实标签',
               xlabel='预测标签')

        # 旋转x轴标签
        plt.setp(ax.get_xticklabels(), rotation=45, ha="right",
                 rotation_mode="anchor")

        # 在混淆矩阵中标记数值
        thresh = cm.max() / 2.
        for i in range(cm.shape[0]):
            for j in range(cm.shape[1]):
                ax.text(j, i, format(cm[i, j], 'd'),
                        horizontalalignment="center",
                        color="white" if cm[i, j] > thresh else "black")

        fig.tight_layout()
        st.pyplot(fig)

        # 特征重要性（系数）
        if X.shape[1] <= 15:  # 特征数量不多时才显示
            st.subheader("特征重要性（系数）")
            coefficients = pd.DataFrame({
                "特征": feature_names,
                "系数": model.coef_[0]
            }).sort_values(by="系数", key=abs, ascending=False)

            fig, ax = plt.subplots(figsize=(10, 6))
            coefficients.plot(kind="barh", x="特征", y="系数", ax=ax)
            ax.axvline(x=0, color='gray', linestyle='--')
            ax.set_title("特征系数（绝对值越大影响越大）")
            st.pyplot(fig)

# 随机森林页面
elif algorithm == "随机森林":
    st.title("随机森林算法学习")

    # 算法原理
    st.header("1. 算法原理")
    st.write("""
    随机森林是一种基于决策树的集成学习算法，它通过构建多个决策树并综合它们的预测结果来提高模型的性能和稳定性。

    ### 基本原理
    随机森林的工作流程包括以下步骤：
    1. 从原始数据集中通过bootstrap抽样（有放回抽样）生成多个子数据集
    2. 为每个子数据集构建一棵决策树，在构建过程中，每个节点只考虑随机选择的一部分特征
    3. 对于分类问题，通过投票决定最终预测结果；对于回归问题，取所有树预测结果的平均值

    ### 优势
    - 具有较高的预测准确率
    - 能处理高维数据，不需要特征选择
    - 能评估特征的重要性
    - 不易过拟合
    - 对缺失值不敏感

    ### 关键参数
    - n_estimators：森林中树的数量
    - max_depth：树的最大深度
    - min_samples_split：分裂内部节点所需的最小样本数
    - min_samples_leaf：叶节点所需的最小样本数
    - max_features：构建树时考虑的最大特征数
    """)

    # 代码示例
    st.header("2. 代码示例")
    st.code("""
    # 随机森林分类示例
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.datasets import load_iris
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score, classification_report

    # 加载数据
    data = load_iris()
    X, y = data.data, data.target

    # 划分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, random_state=42
    )

    # 创建并训练随机森林模型
    model = RandomForestClassifier(n_estimators=100, random_state=42)
    model.fit(X_train, y_train)

    # 预测
    y_pred = model.predict(X_test)

    # 评估
    accuracy = accuracy_score(y_test, y_pred)
    print(f"准确率: {accuracy:.2f}")
    print("分类报告:")
    print(classification_report(y_test, y_pred))

    # 输出特征重要性
    print("特征重要性:")
    for name, importance in zip(data.feature_names, model.feature_importances_):
        print(f"{name}: {importance:.4f}")
    """, language="python")

    # 交互演示
    st.header("3. 试一试")
    st.write("调整参数，观察随机森林算法在不同数据集上的表现")

    # 选择任务类型
    task_type = st.radio("选择任务类型", ["分类", "回归"])

    # 根据任务类型选择数据集
    data_type = "classification" if task_type == "分类" else "regression"
    datasets = get_datasets_by_type(data_type)
    dataset_names = [d[0] for d in datasets]
    dataset_names.append("生成" + task_type + "数据集")

    dataset_name = st.selectbox("选择数据集", dataset_names)

    # 加载数据
    X, y, feature_names = load_selected_dataset(dataset_name)

    # 显示数据集信息
    st.subheader("数据集信息")
    st.write(f"样本数量: {X.shape[0]}, 特征数量: {X.shape[1]}")

    if task_type == "分类":
        class_distribution = pd.Series(y).value_counts()
        st.write("类别分布:")
        st.bar_chart(class_distribution)

    # 显示前5行数据
    st.write("数据预览:")
    df = pd.DataFrame(X, columns=feature_names)
    df["目标值"] = y
    st.dataframe(df.head())

    # 参数设置
    st.subheader("参数设置")
    col1, col2 = st.columns(2)

    with col1:
        test_size = st.slider("测试集比例", 0.1, 0.5, 0.3)
        n_estimators = st.slider("树的数量", 10, 200, 100, step=10)

    with col2:
        max_depth = st.slider("树的最大深度", 1, 30, 10)
        max_depth = None if max_depth == 30 else max_depth  # 30表示不限制深度

        min_samples_split = st.slider("分裂所需最小样本数", 2, 20, 2)

    # 训练模型
    if st.button("训练模型"):
        # 记录用户交互
        log_interaction(
            "随机森林",
            "train_model",
            {
                "task_type": task_type,
                "dataset": dataset_name,
                "test_size": test_size,
                "n_estimators": n_estimators,
                "max_depth": max_depth,
                "min_samples_split": min_samples_split
            }
        )

        # 划分训练集和测试集
        X_train, X_test, y_train, y_test = train_test_split(
            X, y, test_size=test_size, random_state=42,
            stratify=y if task_type == "分类" else None
        )

        # 创建模型
        with st.spinner("正在训练模型..."):
            time.sleep(1)  # 模拟训练时间

            if task_type == "分类":
                model = RandomForestClassifier(
                    n_estimators=n_estimators,
                    max_depth=max_depth,
                    min_samples_split=min_samples_split,
                    random_state=42,
                    n_jobs=-1
                )
            else:
                model = RandomForestRegressor(
                    n_estimators=n_estimators,
                    max_depth=max_depth,
                    min_samples_split=min_samples_split,
                    random_state=42,
                    n_jobs=-1
                )

            # 训练模型
            model.fit(X_train, y_train)

            # 预测
            y_pred = model.predict(X_test)

        # 显示结果
        st.subheader("模型结果")

        if task_type == "分类":
            # 分类结果
            accuracy = accuracy_score(y_test, y_pred)
            st.success(f"准确率: {accuracy:.4f}")

            # 显示分类报告
            st.text("分类报告:")
            st.text(classification_report(y_test, y_pred))

            # 混淆矩阵
            st.subheader("混淆矩阵")
            cm = confusion_matrix(y_test, y_pred)

            fig, ax = plt.subplots()
            im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
            ax.figure.colorbar(im, ax=ax)

            # 标记坐标轴
            classes = np.unique(y)
            ax.set(xticks=np.arange(cm.shape[1]),
                   yticks=np.arange(cm.shape[0]),
                   xticklabels=classes, yticklabels=classes,
                   title='混淆矩阵',
                   ylabel='真实标签',
                   xlabel='预测标签')

            # 旋转x轴标签
            plt.setp(ax.get_xticklabels(), rotation=45, ha="right",
                     rotation_mode="anchor")

            # 在混淆矩阵中标记数值
            thresh = cm.max() / 2.
            for i in range(cm.shape[0]):
                for j in range(cm.shape[1]):
                    ax.text(j, i, format(cm[i, j], 'd'),
                            horizontalalignment="center",
                            color="white" if cm[i, j] > thresh else "black")

            fig.tight_layout()
            st.pyplot(fig)

        else:
            # 回归结果
            mse = mean_squared_error(y_test, y_pred)
            r2 = r2_score(y_test, y_pred)

            st.success(f"均方误差 (MSE): {mse:.4f}")
            st.success(f"决定系数 (R²): {r2:.4f}")

            # 可视化预测值与真实值
            st.subheader("预测值 vs 真实值")
            fig, ax = plt.subplots(figsize=(10, 6))
            ax.scatter(y_test, y_pred, alpha=0.5)
            ax.plot([y.min(), y.max()], [y.min(), y.max()], 'r--')
            ax.set_xlabel('真实值')
            ax.set_ylabel('预测值')
            ax.set_title('预测值与真实值对比')
            st.pyplot(fig)

        # 特征重要性
        st.subheader("特征重要性")
        importance_df = pd.DataFrame({
            "特征": feature_names,
            "重要性": model.feature_importances_
        }).sort_values(by="重要性", ascending=False)

        fig, ax = plt.subplots(figsize=(10, 6))
        importance_df.plot(kind="barh", x="特征", y="重要性", ax=ax)
        ax.set_title("特征重要性")
        st.pyplot(fig)

# 朴素贝叶斯页面
elif algorithm == "朴素贝叶斯":
    st.title("朴素贝叶斯算法学习")

    # 算法原理
    st.header("1. 算法原理")
    st.write("""
    朴素贝叶斯是一种基于贝叶斯定理和特征条件独立性假设的分类算法。它的"朴素"之处在于假设所有特征之间是条件独立的。

    ### 基本原理
    朴素贝叶斯算法基于贝叶斯定理：
    $$ P(A|B) = \\frac{P(B|A)P(A)}{P(B)} $$

    在分类问题中，我们希望根据特征$x_1, x_2, ..., x_n$预测类别$y$，即计算$P(y|x_1, x_2, ..., x_n)$。

    根据贝叶斯定理：
    $$ P(y|x_1, ..., x_n) = \\frac{P(x_1, ..., x_n|y)P(y)}{P(x_1, ..., x_n)} $$

    基于特征条件独立性假设：
    $$ P(x_1, ..., x_n|y) = \\prod_{i=1}^{n} P(x_i|y) $$

    因此，最终的分类规则为：
    $$ y = \\arg\\max_y P(y) \\prod_{i=1}^{n} P(x_i|y) $$

    ### 常见类型
    - 高斯朴素贝叶斯：假设特征服从高斯分布
    - 多项式朴素贝叶斯：适用于离散特征，如文本分类中的词频
    - 伯努利朴素贝叶斯：适用于二进制特征

    ### 优势与不足
    - 优势：计算高效，需要的训练数据少，适合高维数据
    - 不足：特征独立性假设在现实中往往不成立，可能影响分类效果
    """)

    # 代码示例
    st.header("2. 代码示例")
    st.code("""
    # 高斯朴素贝叶斯示例
    from sklearn.naive_bayes import GaussianNB
    from sklearn.datasets import load_iris
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score, classification_report

    # 加载数据
    data = load_iris()
    X, y = data.data, data.target

    # 划分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, random_state=42
    )

    # 创建并训练朴素贝叶斯模型
    model = GaussianNB()
    model.fit(X_train, y_train)

    # 预测
    y_pred = model.predict(X_test)

    # 评估
    accuracy = accuracy_score(y_test, y_pred)
    print(f"准确率: {accuracy:.2f}")
    print("分类报告:")
    print(classification_report(y_test, y_pred))
    """, language="python")

    # 交互演示
    st.header("3. 试一试")
    st.write("调整参数，观察朴素贝叶斯算法在不同数据集上的表现")

    # 选择数据集
    datasets = get_datasets_by_type("classification")
    dataset_names = [d[0] for d in datasets]
    dataset_names.append("生成分类数据集")

    dataset_name = st.selectbox("选择数据集", dataset_names)

    # 加载数据
    X, y, feature_names = load_selected_dataset(dataset_name)

    # 显示数据集信息
    st.subheader("数据集信息")
    st.write(f"样本数量: {X.shape[0]}, 特征数量: {X.shape[1]}")
    class_distribution = pd.Series(y).value_counts()
    st.write("类别分布:")
    st.bar_chart(class_distribution)

    # 显示前5行数据
    st.write("数据预览:")
    df = pd.DataFrame(X, columns=feature_names)
    df["目标值"] = y
    st.dataframe(df.head())

    # 参数设置
    st.subheader("参数设置")
    test_size = st.slider("测试集比例", 0.1, 0.5, 0.3)

    var_smoothing = st.slider(
        "方差平滑系数",
        1e-10, 1e-8, 1e-9,
        help="用于数值稳定性的方差平滑参数，值越大平滑效果越强"
    )

    # 训练模型
    if st.button("训练模型"):
        # 记录用户交互
        log_interaction(
            "朴素贝叶斯",
            "train_model",
            {
                "dataset": dataset_name,
                "test_size": test_size,
                "var_smoothing": var_smoothing
            }
        )

        # 划分训练集和测试集
        X_train, X_test, y_train, y_test = train_test_split(
            X, y, test_size=test_size, random_state=42, stratify=y
        )

        # 创建并训练模型
        with st.spinner("正在训练模型..."):
            time.sleep(1)  # 模拟训练时间
            model = GaussianNB(var_smoothing=var_smoothing)
            model.fit(X_train, y_train)

            # 预测
            y_pred = model.predict(X_test)
            y_pred_proba = model.predict_proba(X_test)

        # 显示结果
        st.subheader("模型结果")

        # 准确率
        accuracy = accuracy_score(y_test, y_pred)
        st.success(f"准确率: {accuracy:.4f}")

        # 分类报告
        st.text("分类报告:")
        st.text(classification_report(y_test, y_pred))

        # 混淆矩阵
        st.subheader("混淆矩阵")
        cm = confusion_matrix(y_test, y_pred)

        fig, ax = plt.subplots()
        im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
        ax.figure.colorbar(im, ax=ax)

        # 标记坐标轴
        classes = np.unique(y)
        ax.set(xticks=np.arange(cm.shape[1]),
               yticks=np.arange(cm.shape[0]),
               xticklabels=classes, yticklabels=classes,
               title='混淆矩阵',
               ylabel='真实标签',
               xlabel='预测标签')

        # 旋转x轴标签
        plt.setp(ax.get_xticklabels(), rotation=45, ha="right",
                 rotation_mode="anchor")

        # 在混淆矩阵中标记数值
        thresh = cm.max() / 2.
        for i in range(cm.shape[0]):
            for j in range(cm.shape[1]):
                ax.text(j, i, format(cm[i, j], 'd'),
                        horizontalalignment="center",
                        color="white" if cm[i, j] > thresh else "black")

        fig.tight_layout()
        st.pyplot(fig)

        # 类先验概率
        st.subheader("类先验概率")
        prior_df = pd.DataFrame({
            "类别": classes,
            "先验概率": model.class_prior_
        })
        st.dataframe(prior_df)

        # 绘制先验概率条形图
        fig, ax = plt.subplots()
        prior_df.plot(kind="bar", x="类别", y="先验概率", ax=ax, legend=False)
        ax.set_title("类先验概率分布")
        ax.set_ylim(0, 1)
        st.pyplot(fig)
